package org.codeanalyzer.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApiLibrary extends ApiItem
{
    final Map<String, ApiPackage> packages = new HashMap<String, ApiPackage>();
    private List<String> packagesToExclude = new ArrayList<String>();

    public ApiLibrary(String name)
    {
        super(name);
    }

    public ApiPackage addPackage(String packageName)
    {
        if (packageName == null)
            throw new IllegalArgumentException("apiPackage must not be null");

        if ( !isAllowAddPackage(packageName))
            return null;

        ApiPackage apiPackage;
        if (packages.containsKey(packageName))
            apiPackage = packages.get(packageName);
        else
            packages.put(packageName, apiPackage = new ApiPackage(packageName));

        return apiPackage;
    }

    private boolean isAllowAddPackage(String packageName)
    {
        return !isPackageInExcludeList(packageName);
    }

    private boolean isAllowAddClass(String packageName, String className, String classModifiers)
    {
        if ( !isAllowAddPackage(packageName))
            return false;
        return classModifiers.contains("public");
    }

    private boolean isAllowAddMethod(String packageName, String className, String classModifiers, String methodName,
        String methodModifiers)
    {
        if ( !isAllowAddClass(packageName, className, classModifiers))
            return false;
        return methodModifiers.contains("public");
    }

    public ApiClass addClass(String packageName, String className, String modifiers)
    {
        if ( !isAllowAddClass(packageName, className, modifiers))
            return null;

        ApiPackage apiPackage = addPackage(packageName);

        return apiPackage.addClass(className);
    }

    public List<ApiPackage> getPackages()
    {
        List<ApiPackage> packageList = new ArrayList<ApiPackage>(packages.values());
        Collections.sort(packageList);
        return packageList;
    }

    public ApiMethod addMethod(String packageName, String className, String classModifiers, String name,
        String modifiers, String returnType, String parameters)
    {
        if ( !isAllowAddMethod(packageName, className, classModifiers, name, modifiers))
            return null;

        ApiPackage apiPackage = addPackage(packageName);
        ApiClass apiClass = apiPackage.addClass(className);

        return apiClass.addMethod(name, modifiers, returnType, parameters);
    }

    public void setExcludePackages(List<String> packagesToExclude)
    {
        this.packagesToExclude = new ArrayList<String>(packagesToExclude);
    }

    public List<String> getPackagesToExclude()
    {
        return packagesToExclude;
    }

    public boolean isPackageInExcludeList(String packageName)
    {
        if (packagesToExclude == null)
            return false;

        for(String excludePackageName : packagesToExclude)
        {
            if (excludePackageName.endsWith(".*"))
            {
                // concat(".") is for excluding partial package name equality
                // such as sun and sunw
                if (packageName.concat(".").startsWith(excludePackageName.replace(".*", "").concat(".")))
                    return true;
            }
            else
            {
                if (packageName.concat(".").equals(excludePackageName.concat(".")))
                    return true;
            }
        }
        return false;
    }

    /**
     * Increments coverage count for the specified method
     * 
     * @param invokedClassFullName the qualified class name, i.e. org.codeanalyzer.test.api.GuineaPig
     * @param methodNameWithParameters the method name with qualified parameters, i.e. main(java.lang.String[])
     */
    public void addMethodCoverage(String invokedClassFullName, String methodNameWithParameters)
    {
        String packageName;
        String className;
        if (invokedClassFullName.contains("."))
        {
            packageName = invokedClassFullName.substring(0, invokedClassFullName.lastIndexOf("."));
            className = invokedClassFullName.substring(invokedClassFullName.lastIndexOf(".") + 1);
        }
        else
        {
            packageName = null;
            className = invokedClassFullName;
        }

        ApiPackage apiPackage = packages.get(packageName);
        if (apiPackage == null)
            return;

        ApiClass apiClass = apiPackage.getClassesMap().get(ApiClass.generateKey(className));
        if (apiClass == null)
        {
            System.err.println("Strange case happend: the class that is used in client code is not found in API. "
                + "Class name: " + className);
            return;
        }

        ApiMethod apiMethod = apiClass.getMethodMap().get(ApiMethod.generateKey(methodNameWithParameters));
        if (apiMethod == null)
        {
            System.err.println("Strange case happend: the method that is used in client code is not found in API. "
                + "Class name: " + className + ", method: " + methodNameWithParameters);
            return;
        }
        apiMethod.incrementCoverageCount();
    }

    public int getTotalCoveredMethods()
    {
        int totalCovered = 0;
        for(ApiPackage apiPackage : packages.values())
            totalCovered += apiPackage.getTotalCoveredMethods();
        return totalCovered;
    }

    public int getTotalCoveredClasses()
    {
        int totalCovered = 0;
        for(ApiPackage apiPackage : packages.values())
            totalCovered += apiPackage.getTotalCoveredClasses();
        return totalCovered;
    }

    public int getTotalCoveredPackages()
    {
        int totalCovered = 0;
        for(ApiPackage apiPackage : packages.values())
            totalCovered += apiPackage.getTotalCoveredClasses() > 0 ? 1 : 0;
        return totalCovered;
    }

    public int getTotalPackages()
    {
        return packages.size();
    }

    public int getTotalClasses()
    {
        int total = 0;
        for(ApiPackage apiPackage : packages.values())
            total += apiPackage.getTotalClasses();
        return total;
    }

    public int getTotalMethods()
    {
        int total = 0;
        for(ApiPackage apiPackage : packages.values())
            total += apiPackage.getTotalMethods();
        return total;
    }

    public int getPackageCoverage()
    {
        return (int)((double)getTotalCoveredPackages() / (double)getTotalPackages() * 100.0);
    }

    public int getClassCoverage()
    {
        return (int)((double)getTotalCoveredClasses() / (double)getTotalClasses() * 100.0);
    }

    public int getMethodCoverage()
    {
        return (int)((double)getTotalCoveredMethods() / (double)getTotalMethods() * 100.0);
    }
}
